use crate::config::ITrusteeConfig;
use crate::constants::{
    GET_REPORT_HANDLER, HASH_ALG, ITRUSTEE_VERSION, PROVISIONING_HANDLER, SCENARIO_NO_AS, RESIZE_LENGTH,
};
use crate::entity::{ItrusteeEvidence, Log};
use base64::engine::general_purpose;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use plugin_manager::{AgentHostFunctions, AgentPlugin, CollectEvidenceParams, PluginBase, PluginError, QueryConfigurationFn};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::error::Error;
use std::ffi::{c_uchar, c_uint, CString};
use uuid::Uuid;

const PLUGIN_NAME: &str = "itrustee";

#[derive(Debug)]
pub struct ITrusteePlugin {
    config: ITrusteeConfig,
}

#[derive(Serialize, Deserialize)]
struct ProvisioningInputPayload {
    version: String,
    scenario: String,
    hash_alg: String,
}

#[derive(Serialize, Deserialize)]
struct ReportInputPayload {
    version: String,
    nonce: String,
    uuid: String,
    hash_alg: String,
    with_tcb: bool,
    request_key: bool,
}

#[derive(Serialize, Deserialize)]
struct ReportInput {
    handler: String,
    payload: Value,
}

impl ITrusteePlugin {
    pub fn new(plugin_type: String, query_configuration: QueryConfigurationFn) -> Result<Self, PluginError> {
        if plugin_type != PLUGIN_NAME {
            return Err(PluginError::InputError("Invalid plugin type".to_string()));
        }
        let plugin_config = query_configuration(plugin_type.clone())
            .ok_or_else(|| PluginError::InternalError("Plugin configuration not found".to_string()))?;
        let config = ITrusteeConfig::from_json(plugin_type, &plugin_config)?;
        Ok(Self { config })
    }

    fn get_itrustee_provision() -> Result<(), PluginError> {
        let payload = serde_json::json!({
            "version": ITRUSTEE_VERSION,
            "scenario": SCENARIO_NO_AS,
            "hash_alg": HASH_ALG
        });

        let provision_input =
            serde_json::to_string(&ReportInput { handler: String::from(PROVISIONING_HANDLER), payload })
                .map_err(|e| PluginError::InternalError(format!("Failed to serialize payload: {}", e)))?;

        let c_string = CString::new(provision_input)
            .map_err(|e| PluginError::InternalError(format!("Failed to create CString: {}", e)))?;

        let mut input_bytes = c_string.as_bytes_with_nul().to_vec();
        let mut input = RaBufferData { size: input_bytes.len() as c_uint, buf: input_bytes.as_mut_ptr() };

        let mut report: Vec<u8> = Vec::new();
        report.resize(RESIZE_LENGTH, 0u8);

        let mut output = RaBufferData { size: report.len() as c_uint, buf: report.as_mut_ptr() as *mut c_uchar };

        unsafe {
            let ret = RemoteAttest(&mut input, &mut output);
            if ret != 0 {
                return Err(PluginError::InternalError("Failed to obtain provision via the QCA library. Please run tlogcat to check the cause of the failure.".to_string()));
            }
        }
        Ok(())
    }

    fn get_itrustee_report(&self, nonce: &[u8], uuid: &str) -> Result<Vec<u8>, PluginError> {
        let report_payload = ReportInputPayload {
            version: String::from(ITRUSTEE_VERSION),
            nonce: URL_SAFE_NO_PAD.encode(nonce),
            uuid: String::from(uuid),
            hash_alg: String::from(HASH_ALG),
            with_tcb: false,
            request_key: true,
        };

        let payload = serde_json::to_value(report_payload)
            .map_err(|e| PluginError::InternalError(format!("Failed to json value: {}", e)))?;

        let report_input = serde_json::to_string(&ReportInput { handler: String::from(GET_REPORT_HANDLER), payload })
            .map_err(|e| PluginError::InternalError(format!("Failed to serialize payload: {}", e)))?;

        let c_string = CString::new(report_input)
            .map_err(|e| PluginError::InternalError(format!("Failed to create CString: {}", e)))?;
        let mut input_bytes = c_string.as_bytes_with_nul().to_vec();
        let mut input =
            RaBufferData { size: input_bytes.len() as c_uint, buf: input_bytes.as_mut_ptr() as *mut c_uchar };
        let mut report: Vec<u8> = Vec::new();
        report.resize(RESIZE_LENGTH, 0u8);
        let mut output = RaBufferData { size: report.len() as c_uint, buf: report.as_mut_ptr() };
        unsafe {
            let ret = RemoteAttest(&mut input, &mut output);
            if ret != 0 {
                return Err(PluginError::InternalError("Failed to obtain evidence via the QCA library. Please run tlogcat to check the cause of the failure.".to_string()));
            }
            report.set_len(
                output
                    .size
                    .try_into()
                    .map_err(|e| PluginError::InternalError(format!("Report length set error: {}", e)))?,
            );
        }
        Ok(report)
    }
}

impl PluginBase for ITrusteePlugin {
    fn plugin_type(&self) -> &str {
        PLUGIN_NAME
    }
}

impl AgentPlugin for ITrusteePlugin {
    fn collect_evidence(&self, evidence_params: CollectEvidenceParams) -> Result<Value, PluginError> {
        ITrusteePlugin::get_itrustee_provision()?;
        let challenge = match evidence_params.nonce {
            Some(nonce) => nonce.to_vec(),
            None => vec![0; 64],
        };

        // get uuid from attester_ids firstly, or from config.
        let ta_uuid = match &evidence_params.attester_ids {
            Some(uuids) => {
                if uuids.is_empty() {
                    &self.config.ta_uuid
                } else if uuids.len() > 1 {
                    return Err(PluginError::InputError("The number of ta_uuid exceeds 1".to_string()));
                } else {
                    uuids
                        .get(0)
                        .ok_or_else(|| PluginError::InputError("Get uuid from attester_ids failed".to_string()))?
                }
            },
            None => &self.config.ta_uuid,
        };

        // validate uuid
        Uuid::parse_str(ta_uuid).map_err(|_| PluginError::InputError(format!("Invalid uuid {}", ta_uuid)))?;

        let mut logs: Vec<Log> = Vec::new();
        let report = self.get_itrustee_report(&challenge, ta_uuid)?;
        logs.push(Log { log_type: "ItrusteeLog".to_string(), log_data: general_purpose::STANDARD.encode(report) });
        let evidence = ItrusteeEvidence { logs };
        serde_json::to_value(evidence)
            .map_err(|e| PluginError::InternalError(format!("Failed to serialize report: {}", e)))
    }
}

#[no_mangle]
pub fn create_plugin(
    host_functions: &AgentHostFunctions,
    plugin_type: &str,
) -> Result<Box<dyn AgentPlugin>, Box<dyn Error>> {
    // Extract the functions from the host functions struct
    let query_configuration = host_functions.query_configuration;

    // Create a new instance with the host functions
    ITrusteePlugin::new(String::from(plugin_type), query_configuration)
        .map(|plugin| Box::new(plugin) as Box<dyn AgentPlugin>)
        .map_err(|e| Box::new(e) as Box<dyn Error>)
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct RaBufferData {
    pub size: c_uint,
    pub buf: *mut u8,
}

#[link(name = "qca")]
unsafe extern "C" {
    pub fn RemoteAttest(input: *mut RaBufferData, out: *mut RaBufferData) -> c_uint;
}